<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>three_customTerrain</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
      .infoWrap {
        position: absolute;
        top: 0;
        left: 0;
      }
    </style>
    <script src="./three.js"></script>
    <script src="./OrbitControls.js"></script>
  </head>
  <body>
      <div class="infoWrap">按下空格键或同时按下shiftLeft键，拖动构建地形</div>
  </body>
  <script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;
    
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  </script>
  <script type="x-shader/x-vertex" id="fragmentShader">
    varying vec2 vUv;
    uniform vec2 xy;
    void main() {
        if(xy.x >= 0.0) {
          float dis = distance(vUv, vec2(xy.y, 1.0-xy.x));
          if(dis < 0.05) {
            gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
          }else {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
          }
          //gl_FragColor = vec4(xy.x, xy.y, 0.0, 1.0);
          
        }else {
          //gl_FragColor = vec4(vUv.x, vUv.y, 0.0, 1.0);
          gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
        //gl_FragColor = vec4(1.0, 0.0, 0.0, 0.6);
      
    }
  </script>

  <script>
    var scene, camera, renderer, clock, controller;
    var shader_material, plane, cmesh, planeGeometry, backPlane, backGeometry
    var height = 100, width = 100;
    var raycaster = new THREE.Raycaster();
    var effectedRadius = 5, k = 1
    var virtualCamera, target, mvpMatrix // shadow properties
    init();
    animate();

    // - Functions -
    function init() {
      scene = new THREE.Scene();
      clock = new THREE.Clock();
      camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
      camera.position.set(100, 100, 100)

      renderer = new THREE.WebGLRenderer({
        antialias: true, // 开启抗锯齿处理
        alpha: true,
      });
      renderer.setSize(window.innerWidth, window.innerHeight);

   

      scene = new THREE.Scene();
     
      postMaterial = new THREE.ShaderMaterial( {
        vertexShader: document.getElementById( 'vertexShader' ).textContent.trim(),
        fragmentShader: document.getElementById( 'fragmentShader' ).textContent.trim(),
        uniforms: {
          xy: { value: new THREE.Vector2(-1, -1) }
        },
        wireframe: true
      } );

      backGeometry = new THREE.PlaneGeometry( width, height, 99, 99 );
      backPlane = new THREE.Mesh( backGeometry, postMaterial );
      backPlane.rotation.x = -Math.PI/2
      scene.add( backPlane );

      controller = new THREE.OrbitControls(camera, renderer.domElement);
      document.body.appendChild(renderer.domElement);
      window.onresize = onResize;

      document.onkeydown = function(e) {
        if(e.code == 'Space') {
          controller.enabled = false
          document.onmousedown = function(downEvent) {
            let { clientX: lastX, clientY: lastY } = downEvent
            
            document.onmousemove = function(moveEvent) { // 设计检测中不应该使用鼠标的事件进行触发
              let { clientX: currentX, clientY: currentY } = moveEvent

              let downResult = judgeMouse(currentX, currentY)
              if(downResult.has) {
                let x = Math.round(downResult.point.z) + 50
                let y = Math.round(downResult.point.x) + 50
                adjustPlaneHeight({ x, y })
                setTextureUV(x/width, y/height)
                backGeometry.computeVertexNormals(); 
              }
            }

            document.onmouseup = function() {
              document.onmousemove = null
            }

            document.onmouseout = function() {
              document.onmousemove = null
            }
          }
          
          document.onkeyup = function() {
            document.onmousemove = null
            document.onmousedown = null
            controller.enabled = true
            setTextureUV(-1, -1)
            backGeometry.computeVertexNormals(); 
          }
        }else if(e.code == 'ShiftLeft') {
          k = -1
          document.onkeyup = function() {
            k = 1
          }
        } 
      }
    }

    function setTextureUV(x, y) {
      postMaterial.uniforms.xy.value = new THREE.Vector2(x, y)
    }

    // 判断当前的鼠标位置有没有选中 Plane 对象
    function judgeMouse(clientX, clientY) {
      let mouse = new THREE.Vector2()
      mouse.x = ( clientX / window.innerWidth ) * 2 - 1,
      mouse.y = -( clientY / window.innerHeight ) * 2 + 1
      
      raycaster.setFromCamera( mouse, camera );
      let intersection = raycaster.intersectObject( backPlane )
      if(intersection.length > 0) {
        return {
          has: true,
          point: intersection[0].point
        }
      }else {
        return {
          has: false
        }
      }
    }

    function adjustPlaneHeight(center) {
      addPointValue(backGeometry, height, width, center, -1, effectedRadius, 'easeInOut')
    }

    function addPointValue(geometry, height, width, center, value, r, type) {
      for (let x = Math.max(center.x - r, 1); x <= Math.min(center.x + r, width); x++ ) {

        for ( let y = Math.max(center.y - r, 1); y <= Math.min(center.y + r, height); y++ ) {
          let distance = Math.sqrt( Math.pow(center.x - x, 2) + Math.pow(center.y - y, 2) );
          if (distance <= r) {
            // geometry.vertices[(x - 1) * height + y - 1].z += 1
            geometry.vertices[(x - 1) * height + y - 1].z += k * value * getHeightScale(distance, r, type);
            // (x - 1) * height + y 数组中的位置
            // -1 数组从 0 开始 计数
            geometry.verticesNeedUpdate = true
          }
        }
      }
    }

     // 根据受影响的边界和距离中心点的距离获取坡度值的收缩比例
     function getHeightScale(distance, r, type = "easeInOut") {
      // distance <= r
      switch (type) {
        case "easeInOut":
          return 1 - (Math.cos(Math.PI + (Math.PI * distance) / r) + 1) / 2;
        case "easeIn":
          return (-Math.pow((distance / r) * 3, 2) + 9) / 9;
        case "easeOut":
          return Math.pow((3 * distance) / r - 3, 2) / 9;
        default:
          return 1 - (Math.cos(Math.PI + (Math.PI * distance) / r) + 1) / 2;
      }
    }

    function animate() {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
      controller.update(clock.getDelta());
    }

    function onResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }
  </script>
</html>
